// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 支持测试覆盖率。

package testing

import (
	"fmt"
	"os"
	"sync/atomic"
)

// CoverBlock记录单个基本块的覆盖率数据。
// 字段是1索引的，就像在编辑器中一样：例如，
// 文件的开头行是1号。列的测量单位为字节。
// 注意：此结构是测试基础结构的内部结构，可能会更改。
// Go 1兼容性指南尚未涵盖它。
type CoverBlock struct {
	Line0 uint32 // 块开始的行号。
	Col0  uint16 // 块开始的列号。
	Line1 uint32 // 块端的行号。
	Col1  uint16 // 块结束的列号。
	Stmts uint16 // 此块中包含的语句数。
}

var cover Cover

// 封面记录有关测试覆盖率检查的信息。
// 注意：此结构是测试基础结构的内部结构，可能会更改。
// Go 1兼容性指南尚未涵盖它。
type Cover struct {
	Mode            string
	Counters        map[string][]uint32
	Blocks          map[string][]CoverBlock
	CoveredPackages string
}

// Coverage将当前代码覆盖率报告为[0，1]范围内的分数。
// 如果未启用覆盖率，则覆盖率返回0。
// 
// 在运行大量顺序测试用例时，检查每一个测试用例后的覆盖率
// 对于确定哪些测试用例使用新的代码路径非常有用。
// /它不能替代“go test-cover”和
// “go tool cover”生成的报告。
func Coverage() float64 {
	var n, d int64
	for _, counters := range cover.Counters {
		for i := range counters {
			if atomic.LoadUint32(&counters[i]) > 0 {
				n++
			}
			d++
		}
	}
	if d == 0 {
		return 0
	}
	return float64(n) / float64(d)
}

// RegisterCover记录测试的覆盖率数据累加器。
// 注意：此函数是测试基础结构的内部函数，可能会更改。
// Go 1兼容性指南尚未涵盖它。
func RegisterCover(c Cover) {
	cover = c
}

// mustBeNil检查错误，如果存在，则报告错误并退出。
func mustBeNil(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "testing: %s\n", err)
		os.Exit(2)
	}
}

// coverReport报告覆盖率百分比，并在需要时写入覆盖率配置文件。
func coverReport() {
	var f *os.File
	var err error
	if *coverProfile != "" {
		f, err = os.Create(toOutputDir(*coverProfile))
		mustBeNil(err)
		fmt.Fprintf(f, "mode: %s\n", cover.Mode)
		defer func() { mustBeNil(f.Close()) }()
	}

	var active, total int64
	var count uint32
	for name, counts := range cover.Counters {
		blocks := cover.Blocks[name]
		for i := range counts {
			stmts := int64(blocks[i].Stmts)
			total += stmts
			count = atomic.LoadUint32(&counts[i]) // For-mode=atomic。
			if count > 0 {
				active += stmts
			}
			if f != nil {
				_, err := fmt.Fprintf(f, "%s:%d.%d,%d.%d %d %d\n", name,
					blocks[i].Line0, blocks[i].Col0,
					blocks[i].Line1, blocks[i].Col1,
					stmts,
					count)
				mustBeNil(err)
			}
		}
	}
	if total == 0 {
		fmt.Println("coverage: [no statements]")
		return
	}
	fmt.Printf("coverage: %.1f%% of statements%s\n", 100*float64(active)/float64(total), cover.CoveredPackages)
}
